package gamebackend;
import framework.Color;
import framework.GraphicsWrapper;
import framework.Point;
import generators.BasicBlockGenerator;
import generators.BlockRule;
import generators.LaserGenerator;
import generators.VerticalScaredGenerator;
import generators.VerticalStreakGenerator;
import gui.BlockStyle;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;



public class LaserCrystalBiome extends GameSession.Biome {

	private static final Block PURPLE = new Block(BlockStyle.THORN, new Color(150, 100, 250));
	private static final int my_yOffset = 10;
	
	private final Block my_blue;
	private HazardPath my_hardPath;
	private HazardPath my_easyPath;
	private final Ship my_ship;
	
	private double my_distanceToLaser;
	private double my_distanceToSwitch;
	private double my_chance = .5;
	private boolean my_together = true;
	
	private final double my_minLaserWidth;
	private final double my_maxLaserWidth;
	/**
	 * All the hazards currently passing through the top of the screen.
	 */
	/*
	 * I don't expect many hazards to exist at the same time.
	 */
	private List<EnviroLaser> my_hazards;
	/**
	 * The length of the hazard that will expire soonest.
	 */
	private double my_shortestLength;
	
	private final BlockLayer my_layer;
	public LaserCrystalBiome(final GameSession the_session, final Ship the_ship,
			final BlockLayer the_layer,	int the_tier, Random the_random, double the_length) {
		the_session.super(the_session, the_tier, the_random, the_length);
		setFlier(new MultiFlier());
		final Color blue;
		if (the_tier == 1) {
			blue = new Color(190, 230, 240);
		} else {
			blue = new Color(70, 100, 250);
		}
		my_blue = new Block(BlockStyle.THORN, blue);
		if (the_tier == 0) {
			my_minLaserWidth = 20;
			my_maxLaserWidth = 35;
		} else if (the_tier == 1) {
			my_minLaserWidth = 80;
			my_maxLaserWidth = 110;
		} else {
			my_minLaserWidth = 20;
			my_maxLaserWidth = getWidth();
		}
		my_hazards = new ArrayList<EnviroLaser>();
		my_ship = the_ship;
		addHardPath(new FlightPath(Block.SECONDARY_PATH, getHardPath(), 16), getLength());
		addEasyPath(new FlightPath(Block.PRIMARY_PATH, getEasyPath(), 30), getLength());
		addGenerator(new VerticalScaredGenerator(my_blue, getRandom(), .01), getLength());
		addGenerator(new VerticalScaredGenerator(PURPLE, getRandom(), .01), getLength());
		addGenerator(new VerticalStreakGenerator(getRandom(), .5), getLength());
		
//		addGenerator(new BasicBlockGenerator(my_blue, BlockRule.EMPTY, getRandom(), 1), getLength());
		
		my_layer = the_layer;
		
		my_hardPath = new HazardPath(getHardPath(), 120);
		my_easyPath = new HazardPath(getEasyPath(), 0);
	}
	
	public void step(final double the_yChange) {
		super.step(the_yChange);
		
		

		my_shortestLength -= the_yChange;
		if (my_shortestLength < 0) {
			// remove hazards that have passed and keep track of the shortest hazard to know when
			// to remove hazards again.
			double min = Double.MAX_VALUE;
			for (Iterator<EnviroLaser> it = my_hazards.iterator(); it.hasNext(); ) {
				EnviroLaser hazard = it.next();
				// length left is in relation to the ship making the path, and not the players ship,
				// so the danger zone starts where the hazard starts.
				final double lengthLeft = my_yOffset + hazard.getRemaining();
				if (lengthLeft < 0) {
					it.remove();
				} else if (lengthLeft < min) {
					min = lengthLeft;
				}
			}
			my_shortestLength = min;
			my_hardPath.updateBounds();
			my_easyPath.updateBounds();
		}
		
		
		
		
		
		
		if (Math.abs(my_hardPath.getTarget() - getHardPath().getXPosition()) < 1) {
			final double target = my_hardPath.getLeftShipBound() +
					getRandom().nextDouble() * (my_hardPath.getRightShipBound()
							- my_hardPath.getLeftShipBound());
			
			// make dead end
			if (getRandom().nextDouble() < .2) {
				FlightPath path = new FlightPath(Block.SECONDARY_PATH, getHardPath(),
						20);
				path.getShipFlier().setTarget(2 * my_hardPath.getTarget() - target);
				addFlightPath(path, 200);
			}
			my_hardPath.setTarget(target);
		}
		if (Math.abs(my_easyPath.getTarget() - getEasyPath().getXPosition()) < 1) {
			if (my_together) {
				my_easyPath.setTarget(getHardPath().getXPosition());
			} else {
				my_easyPath.setTarget(my_easyPath.getLeftShipBound() +
						getRandom().nextDouble() * (my_easyPath.getRightShipBound()
								- my_easyPath.getLeftShipBound()));
			}
		}
		
		my_distanceToSwitch -= the_yChange;
		if (my_distanceToSwitch < 0) {
			my_together = !my_together;
			if (my_together) {
				my_distanceToSwitch += 400 + getRandom().nextDouble() * 300;
			} else {
				my_distanceToSwitch += 800 + + getRandom().nextDouble() * 200;
			}
		}
		
		
		
		
		
		
		
		my_distanceToLaser -= the_yChange;
		if (my_distanceToLaser < 0) {
			my_distanceToLaser += 100 * (1 - my_chance);
			if (getRandom().nextDouble() < my_chance) {
				// construct a list of ranges where a laser can spawn
				final HazardPath first;
				final HazardPath second;
				if (my_hardPath.getLeftSafeZone() < my_easyPath.getLeftSafeZone()) {
					first = my_hardPath;
					second = my_easyPath;
				} else {
					first = my_easyPath;
					second = my_hardPath;
				}
				
				double totalLength = 0;
				
				double left = -my_minLaserWidth / 10;
				double right = first.getLeftSafeZone();
				
				List<Point> laserables = new ArrayList<Point>(3);
				if (right - left > my_minLaserWidth) {
					totalLength += right - left;
					laserables.add(new Point(left, right));
				}
				
				left = first.getRightSafeZone();
				right = second.getLeftSafeZone();
				if (right - left > my_minLaserWidth) {
					totalLength += right - left;
					laserables.add(new Point(left, right));
				}
					
				left = Math.max(first.getRightSafeZone(), second.getRightSafeZone());
				right = getWidth() + my_minLaserWidth / 10;
				if (right - left > my_minLaserWidth) {
					totalLength += right - left;
					laserables.add(new Point(left, right));
				}
				
				
				
				// determine which laserable area gets the laser.
				totalLength *= nextTriangle(.5);
				Point laserAt = null;
				for (Point p : laserables) {
					totalLength -= p.getY() - p.getX();
					if (totalLength <= 0) {
						laserAt = p;
						break;
					}
				}
				
				if (laserAt != null) {
					// fire a laser within the laserable area
					final double width = my_minLaserWidth + getRandom().nextDouble()
							* (Math.min(my_maxLaserWidth, laserAt.getY() - laserAt.getX()) - my_minLaserWidth);
					double ratio = getRandom().nextDouble() * .25;
					if (getRandom().nextInt(2) == 0) {
						ratio = 1 - ratio;
					}
					final double laserLeft = laserAt.getX() +
							ratio * (laserAt.getY() - laserAt.getX() - width);
					
					
					addLaser(laserLeft, laserLeft + width);
					/*debug by making a laser at all laserable areas
					for (Point p : laserables) {
						final EnviroLaser laser = new EnviroLaser(p.getX(),
								p.getY(),
								getGameSession().getPreferredShipYPosition(),
								getGameSession().getHeight(),
								getGameSession().getTotalHeight() - getGameSession().getPreferredShipYPosition() + my_yOffset,// - my_layer.getGenAreaStart() + my_layer.getPathOffset(),
								getHardPath().getShipYVelocity(), my_ship.getYVelocity());
						getGameSession().addEnviroLaser(laser);
					}//*/
					
					my_chance /= 3.5;
				} else {
					my_chance *= 1.3;
				}
				// the distance before the next laser depends on how many areas the laser can appear
				// (this can subtract in the case of 3 laserables)
				my_distanceToLaser += 50 * (2 - laserables.size());
			} else {
				my_chance *= 1.3;
			}
			if (my_chance > 1) {
				my_chance = 1;
			}
			
		}
		
	
		
		
				
	}
	private void addLaser(final double the_left, final double the_right) {
		final EnviroLaser laser = new EnviroLaser(the_left,
				the_right,
				getGameSession().getPreferredShipYPosition(),
				getGameSession().getHeight(),
				getGameSession().getTotalHeight() - getGameSession().getPreferredShipYPosition() + my_yOffset,// - my_layer.getGenAreaStart() + my_layer.getPathOffset(),
				getHardPath().getShipYVelocity(), my_ship.getYVelocity());
		LaserGenerator gen = new LaserGenerator(my_layer.getBlockWidth(), my_layer.getBlockHeight(), laser,
				getGameSession().getTotalHeight() - getGameSession().getHeight() + my_layer.getGenAreaStart() + my_yOffset);
		addGenerator(gen, gen.getPreferredLength());
		getGameSession().addEnviroLaser(laser);
		my_hazards.add(laser);
		my_shortestLength = -1;
	}
	
	
	public void render(GraphicsWrapper g) {
//		g.setColor(Color.GRAY);
//		g.fillRect(0, (int)(getGameSession().getHeight() - getGameSession().getTotalHeight()), getWidth(), 500);
		HazardPath path = my_hardPath;
		g.setColor(Color.YELLOW);
		final double left = path.getLeftSafeZone();
		g.fillRect((int)left, (int)(getGameSession().getHeight() - getGameSession().getTotalHeight()),
				(int)(path.getRightSafeZone() - left), 40);
		g.setColor(Color.RED);
		g.fillRect((int)path.getTarget() - 10, (int)(getGameSession().getHeight() - getGameSession().getTotalHeight()),
				20, 40);
	}
	/**
	 * @param the_median
	 * @return the next random value from a triangular distribution with the_median as its top vertex.
	 */
	private double nextTriangle(final double the_median) {
		final double x = getRandom().nextDouble();
		final double y = getRandom().nextDouble();
		if (x < the_median) {
			final double slope = 1 / the_median;
			if (slope * x > y) {
				return x;
			} else {
				return the_median - x;
			}
		} else {
			final double slope = 1 / (1 - the_median);
			if (slope * (1 - x) > y) {
				return x;
			} else {
				return the_median + 1 - x;
			}
		}
	}
	/**
	 * Contains extra info for a FlightPath relevant for determining where Hazards
	 * should be placed.
	 * @author Jonathan Caddey
	 *
	 */
	private class HazardPath {
		private final FlightPath my_path;
		
		/**
		 * The x position where this path is heading.
		 */
		private double my_target;
		
		/**
		 * The minimum x the ship can safely travel without hitting a Hazard.
		 */
		private double my_leftShipBound;
		
		/**
		 * The maximum x the ship can safely travel without hitting a Hazard.
		 */
		private double my_rightShipBound;
		
		
		public HazardPath(final FlightPath the_path,
				final double the_currentTarget) {
			my_path = the_path;
			my_path.getShipFlier().setTarget(the_currentTarget);
			my_target = the_currentTarget;
		}
		public void setTarget(final double the_target) {
			my_target = the_target;
			my_path.getShipFlier().setTarget(the_target);
		}
		public double getTarget() { 
			return my_target;
		}

		/**
		 * @return the minimum x the ship can safely travel without hitting a Hazard.
		 */
		public double getLeftShipBound() {
			return my_leftShipBound;
		}

		/**
		 * @return the maximum x the ship can safely travel without hitting a Hazard.
		 */
		public double getRightShipBound() {
			return my_rightShipBound;
		}
		
		/**
		 * Given the boundaries of a new Hazard, updates the left and right
		 * bounds of where the flier can safely fly.
		 * @param the_left
		 * @param the_right
		 */
		private void updateBounds(final double the_left, final double the_right) {
			final double x = my_path.getXPosition();
			if (x > the_right && the_right > my_leftShipBound) {
				my_leftShipBound = the_right;
			}
			if (x < the_left && the_left < my_rightShipBound) {
				my_rightShipBound = the_left;
			}
		}
		
		/**
		 * Updates the boundaries of where the Flier can safely fly.
		 */
		public void updateBounds() {
			my_leftShipBound = 0;
			my_rightShipBound = getWidth();
			for (Hazard hazard : my_hazards) {
				updateBounds(hazard.getLeft(), hazard.getLeft() + hazard.getWidth());
			}
		}
		private double margin = 10;
		
		/**
		 * @return the lower x side of the area
		 * in which there can be no Hazards without 
		 * threatening the ship.
		 */
		public double getRightSafeZone() {
			if (Math.abs(my_target - my_path.getXPosition()) < 4) {
				return my_target + margin;
			}
			if (my_target > my_path.getXPosition()) {
				return my_target + margin;
			} else if (my_path.getXVelocity() < 0) {
				return my_path.getXPosition() + margin;
			} else {
				return getWidth();
			}
		}
		
		/**
		 * @return the larger x side of the area
		 * in which there can be no Hazards without 
		 * threatening the ship.
		 */
		public double getLeftSafeZone() {
			if (Math.abs(my_target - my_path.getXPosition()) < 4) {
				return my_target - margin;
			}
			if (my_target < my_path.getXPosition()) {
				return my_target - margin;
			} else if (my_path.getXVelocity() > 0){
				return my_path.getXPosition() - margin;
			} else {
				return 0;
			}
		}
		
	}

}
